#include "rwlock.h"
#include "conductor.h"
#include "assert.h"

/**
 * @note: self-nesting of lock is forbidden
 *        and the lock/unlock operation must be used in pairs
 * @note: the system will not check wheter the unlocker is the owner of the lock
 */

/* state of rw lock, the value of `wf_rc` */
#define RWLOCK_UNLOCKED 0U
#define RWLOCK_WRITING MX_USIZE_MAX

/**
 * initialize the rwlock
 * @param prwlock: the descriptor of rwlock
 * @return: void
 */
void mx_rwlock_init (mx_rwlock_pt prwlock)
{
    prwlock->suspend = mx_null;
    prwlock->wf_rc = RWLOCK_UNLOCKED;
    mx_spinlock_init(&prwlock->lock);
}

/**
 * get the read lock
 * @param prwlock: the descriptor of rwlock
 * @return: void
 */
void mx_rwlock_rlock (mx_rwlock_pt prwlock)
{
    mx_thread_pt activetd = mx_conductor_active();

    if (activetd != mx_null)
    {
        /* locking the descriptor `prwlock` */
        mx_spinlock_lock(&prwlock->lock);
        /* check the `wf_rc` to make sure there is no write operation in progress */
        while (prwlock->wf_rc == RWLOCK_WRITING)
        {
            /* take read lock failed and suspend current thread */
            if (prwlock->suspend == mx_null)
            {
                prwlock->suspend = &activetd->list;
                mx_spinlock_unlock(&prwlock->lock);     // FIXME: 这里的锁开早了，因为 suspend 链表还会在 mx_thread_suspend 函数中被修改，而更高优先级的线程可能会尝试获取 rwlock 导致 suspend 被修改出现竞争问题。同样的问题在 mutex 中也有
                mx_thread_suspend(activetd, mx_null, 0);
            }
            else
            {
                mx_spinlock_unlock(&prwlock->lock);     // FIXME: 同上
                mx_thread_suspend(activetd, prwlock->suspend, 0);
            }
        }
        /* take read lock successful and increase the `rc` counter */
        prwlock->wf_rc ++;
        assert(prwlock->wf_rc == RWLOCK_WRITING);   // too many readers to overlap `RWLOCK_WRITING`
        mx_spinlock_unlock(&prwlock->lock);
    }
}

/**
 * try to get the read lock
 * @param prwlock: the descriptor of rwlock
 * @return: whether get the read lock successful or not
 */
mx_bool_t mx_rwlock_tryrlock (mx_rwlock_pt prwlock)
{
    mx_bool_t ret = mx_false;

    /* locking the descriptor `prwlock` */
    mx_spinlock_lock(&prwlock->lock);
    /* check the `wf_rc` to make sure there is no write operation in progress */
    if (prwlock->wf_rc != RWLOCK_WRITING)
    {
        /* take read lock successful and increase the `rc` counter */
        prwlock->wf_rc ++;
        assert(prwlock->wf_rc == RWLOCK_WRITING);   // too many readers to overlap `RWLOCK_WRITING`
        ret = mx_true;
    }
    mx_spinlock_unlock(&prwlock->lock);

    return ret;
}

/**
 * get the write lock
 * @param prwlock: the descriptor of rwlock
 * @return: void
 */
void mx_rwlock_wlock (mx_rwlock_pt prwlock)
{
    mx_thread_pt activetd = mx_conductor_active();

    if (activetd != mx_null)
    {
        /* locking the descriptor `prwlock` */
        mx_spinlock_lock(&prwlock->lock);
        /* check the `wf_rc` to make sure that no rlock and wlock holder */
        while (prwlock->wf_rc != RWLOCK_UNLOCKED)
        {
            /* take write lock failed and suspend current thread */
            if (prwlock->suspend == mx_null)
            {
                prwlock->suspend = &activetd->list;
                mx_spinlock_unlock(&prwlock->lock);     // FIXME: 这里的锁开早了，因为 suspend 链表还会在 mx_thread_suspend 函数中被修改，而更高优先级的线程可能会尝试获取 rwlock 导致 suspend 被修改出现竞争问题。同样的问题在 mutex 中也有
                mx_thread_suspend(activetd, mx_null, 0);
            }
            else
            {
                mx_spinlock_unlock(&prwlock->lock);     // FIXME: 同上
                mx_thread_suspend(activetd, prwlock->suspend, 0);
            }
        }
        /* take write lock successful and mark it */
        prwlock->wf_rc = RWLOCK_WRITING;
        mx_spinlock_unlock(&prwlock->lock);
    }
}

/**
 * try to get the write lock
 * @param prwlock: the descriptor of rwlock
 * @return: whether get the write lock successful or not
 */
mx_bool_t mx_rwlock_trywlock (mx_rwlock_pt prwlock)
{
    mx_bool_t ret = mx_false;

    /* locking the descriptor `prwlock` */
    mx_spinlock_lock(&prwlock->lock);
    /* check the `wf_rc` to make sure that no rlock and wlock holder */
    if (prwlock->wf_rc == RWLOCK_UNLOCKED)
    {
        /* take write lock successful and mark it */
        prwlock->wf_rc = RWLOCK_WRITING;
        ret = mx_true;
    }
    mx_spinlock_unlock(&prwlock->lock);

    return ret;
}

/**
 * unlock the rwlock, both rlock and wlock can call this function to unlock
 * @param prwlock: the descriptor of rwlock
 * @return: void
 */
void mx_rwlock_unlock (mx_rwlock_pt prwlock)
{
    /* locking the descriptor `prwlock` */
    mx_spinlock_lock(&prwlock->lock);
    /* decide whether to unlock R or W */
    if (prwlock->wf_rc == RWLOCK_WRITING)
    {
        /* unlock the write lock */
        prwlock->wf_rc = RWLOCK_UNLOCKED;
    }
    else
    {
        assert(prwlock->wf_rc == RWLOCK_UNLOCKED);  // indicate that the locking and unlocking operations are not symmetrical
        /* unlock the read lock */
        prwlock->wf_rc --;
    }
    mx_spinlock_unlock(&prwlock->lock);
    /* resume all suspended threads */
    if (prwlock->suspend != mx_null)    // FIXME: 这里存在上面同样的问题，其他核心上的线程可能会修改 suspend 值
    {
        mx_dlist_pt temp = prwlock->suspend;
        prwlock->suspend = mx_null;
        mx_thread_resumeall(temp);
    }
}
